package com.wdb.modules.quartz.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronException;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wdb.exception.BadRequestException;
import com.wdb.modules.quartz.domain.QuartzJob;
import com.wdb.modules.quartz.domain.QuartzLog;
import com.wdb.modules.quartz.domain.dto.QuartJobExcelTDO;
import com.wdb.modules.quartz.domain.vo.QuartzJobQueryCriteria;
import com.wdb.modules.quartz.mapper.QuartzJobMapper;
import com.wdb.modules.quartz.mapper.QuartzLogMapper;
import com.wdb.modules.quartz.service.QuartzJobService;
import com.wdb.modules.quartz.utils.QuartzManage;
import com.wdb.utils.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * @author: wendaobai@qq.com
 * @date: 2024/8/21 星期三 10:23
 */
@Slf4j
@RequiredArgsConstructor
@Service(value = "quartzJobService")
public class QuartzJobServiceImpl extends ServiceImpl<QuartzJobMapper, QuartzJob>
    implements QuartzJobService {

  private final QuartzJobMapper quartzJobMapper;
  private final QuartzLogMapper quartzLogMapper;

  private final QuartzManage quartzManage;
  private final RedisUtils redisUtils;

  @Override
  public PageResult<QuartzJob> queryAll(QuartzJobQueryCriteria criteria, Page<Object> page) {

    return PageUtil.toPage(quartzJobMapper.findAll(criteria, page));
  }
  @Override
  public List<QuartzJob> queryAll(QuartzJobQueryCriteria criteria) {

    return quartzJobMapper.findAll(criteria);
  }

  @Override
  public QuartzJob findById(Long id) {
    if (id != null) {
      return quartzJobMapper.selectById(id);
    }
    return null;
  }

  @Override
  public void execution(QuartzJob quartzJob) {

    quartzManage.runJobNow(quartzJob);
  }

  @Async
  @Override
  @Transactional(rollbackFor = Exception.class)
  /**
   * 执行子任务 遍历任务数组，对于每个子任务ID，从数据库获取对应的QuartzJob对象，设置UUID，然后执行
   * 通过Redis检查每个子任务的执行状态，如果子任务执行失败，则停止后续子任务的执行
   *
   * @param tasks 子任务ID数组
   */
  public void executionSubJob(String[] tasks) {
    for (String id : tasks) {
      // 跳过空白的ID
      if (StrUtil.isBlank(id)) {
        continue;
      }
      // 通过ID从数据库中获取QuartzJob对象
      QuartzJob quartzJob = getById(Long.parseLong(id));
      // 跳过不存在的QuartzJob
      if (quartzJob == null) {
        continue;
      }
      // 设置uuid
      String uuid = StrUtil.uuid();
      quartzJob.setUuid(uuid);
      this.execution(quartzJob);
      // redis获取执行状态，如果执行失败则停止后面的子任务执行
      Boolean result = (Boolean) redisUtils.get(uuid);
      // 休眠5秒，再次获取子任务执行情况
      while (result == null) {
        log.info("while--{}", result);
        try {
          Thread.sleep(5000);
        } catch (InterruptedException e) {
          log.error(e.getMessage(), e);
        }
        result = (Boolean) redisUtils.get(uuid);
      }
      // 如果子任务执行失败，则清除UUID并中断循环
      if (!result) {
        redisUtils.del(uuid);
        break;
      }
    }
  }

  @Override
  public void updateIsPause(QuartzJob quartzJob) {
    if (quartzJob.getIsPause()) {
      quartzManage.resumeJob(quartzJob);
      quartzJob.setIsPause(false);
    } else {
      quartzManage.pauseJob(quartzJob);
      quartzJob.setIsPause(true);
    }
    saveOrUpdate(quartzJob);
  }

  /**
   * 创建一个新的QuartzJob并将其添加到调度管理中
   *
   * @param quartzJob 要创建的QuartzJob对象，包含执行逻辑和调度信息
   * @throws BadRequestException 如果cron表达式格式无效，则抛出此异常
   */
  @Override
  public void create(QuartzJob quartzJob) {
    // 校验cron表达式的格式以确保有效性
    if (!CronExpression.isValidExpression(quartzJob.getCronExpression())) {
      throw new BadRequestException("cron表达式格式错误");
    }
    // 保存Job信息到数据库
    save(quartzJob);
    // 将Job添加到Quartz调度器中以开始执行
    quartzManage.addJob(quartzJob);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void update(QuartzJob resources) {
    // 校验cron表达式的格式以确保有效性
    if (!CronExpression.isValidExpression(resources.getCronExpression())) {
      throw new BadRequestException("cron表达式格式错误");
    }
    // 子任务中不能添加当前任务ID
    if (StringUtils.isNotBlank(resources.getSubTask())) {
      List<String> subTasks = Arrays.asList(resources.getSubTask().split("[,，]"));
      if (subTasks.contains(resources.getId().toString())) {
        throw new BadRequestException("子任务中不能添加当前主任务");
      }
    }
    saveOrUpdate(resources);
    quartzManage.updateJobCron(resources);
  }

  /**
   * 重写删除方法，实现批量删除QuartzJob 通过传入一系列ID，查找对应的QuartzJob对象并删除它们 使用事务控制，确保数据的一致性与安全
   * 如果遇到任何异常，则回滚事务，避免数据不一致
   *
   * @param ids 要删除的QuartzJob的ID集合
   */
  @Override
  @Transactional(rollbackFor = Exception.class)
  public void delete(Set<Long> ids) {
    // 遍历ID集合，逐一处理每个ID
    for (Long id : ids) {
      // 尝试根据当前ID获取QuartzJob实例
      QuartzJob quartzJob = getById(id);
      // 如果获取的QuartzJob为空，则跳过当前循环，处理下一个ID
      if (quartzJob == null) {
        continue;
      }
      // 调用Quartz管理类删除对应的Job
      quartzManage.deleteJob(quartzJob);
      // 从数据库中删除当前的QuartzJob记录
      this.removeById(quartzJob);
    }
  }

  @Override
  public PageResult<QuartzLog> queryAllLog(QuartzJobQueryCriteria criteria, Page<Object> page) {
    IPage<QuartzLog> list = quartzLogMapper.findAll(criteria, page);
    return PageUtil.toPage(list);
  }

  @Override
  public void download(List<QuartzJob> list, HttpServletResponse response) {
    ArrayList<QuartJobExcelTDO> quartJobExcelTDOS = new ArrayList<>();
    list.forEach(
        quartzJob -> {
          QuartJobExcelTDO quartJobExcelTDO = new QuartJobExcelTDO();
          quartJobExcelTDO.setMethodName(quartzJob.getMethodName());
          quartJobExcelTDO.setParams(quartzJob.getParams());
          quartJobExcelTDOS.add(quartJobExcelTDO);
        });
    FileUtil.exportToExcel(response,quartJobExcelTDOS , QuartJobExcelTDO.class);
  }
}
